﻿using System;
using System.Threading.Tasks;
using Pay.DTO;
using Pay.Core;
using Flurl.Http;
using System.Collections.Generic;
using System.Web.Security;
using System.Security.Cryptography;
using System.Security.Cryptography.X509Certificates;
using System.Web;
using System.Net.Http;

namespace Pay.Service
{
	public class kltPaySign{
		public kltPaySign(){
		}
#if DEBUG
		protected string api_url = "http://opsweb.koolyun.cn";
		/// <summary>
		/// 秘钥
		/// </summary>
		private string key = "1234567890";
#else
		protected string api_url = "https://query.openepay.com";
		/// <summary>
		/// 秘钥
		/// </summary>
		private string key = "1234567890";
#endif
		public async Task<T> PayGateway<T>(klt_gateway_base model, string url) where T : klt_refundOrQuery_result, new() {
			var log = IocHelper.Get<ILogHelper>("info");
			var content = _sign(model);
			try {
				var formUrlContent = new FormUrlEncodedContent(content);
				var resp1 = await (api_url + url).PostAsync(formUrlContent).ReceiveString();
				log.Info(resp1);
				if (resp1.Contains("ERRORCODE"))
					return null;
				return resp1.ToObject<T>();
			} catch (FlurlHttpException ex) {
				log.Error(ex.Message);
				return null;
			}
		}

		private Dictionary<string, string> _sign(klt_gateway_base model) {
			var content = new Dictionary<string, string>();
			string json = model.ToJson();
			var collection = json.ToObject<Dictionary<string, object>>();
			collection.Remove("signMsg");
			string result = string.Empty;
			foreach (var kv in collection) {
				if (kv.Value != null && !string.IsNullOrEmpty(kv.Value.ToString())) {
					result += "&" + kv.Key + "=" + kv.Value;
					content.Add(kv.Key, kv.Value.ToString());
				}
			}
			result = result.Trim('&');
			result += "&key=" + key;
			var log = IocHelper.Get<ILogHelper>("info");
			log.Info(result);
			string sign = FormsAuthentication.HashPasswordForStoringInConfigFile(result, "MD5");
			log.Info(sign);
			content.Add("signMsg", sign);
			return content;
		}

		public string _sign(klt_gateway_pay model) {
			var content = new Dictionary<string, string>();
			string json = model.ToJson();
			var collection = json.ToObject<Dictionary<string, object>>();
			collection.Remove("signMsg");
			string result = string.Empty;
			foreach (var kv in collection) {
				if (kv.Value != null && !string.IsNullOrEmpty(kv.Value.ToString())) {
					result += "&" + kv.Key + "=" + kv.Value;
					content.Add(kv.Key, kv.Value.ToString());
				}
			}
			result = result.Trim('&');
			result += "&key=" + key;
			var log = IocHelper.Get<ILogHelper>("info");
			log.Info(result);
			string sign = FormsAuthentication.HashPasswordForStoringInConfigFile(result, "MD5");
			log.Info(sign);
			return sign;
		}

		/// <summary>
		/// 获取原始签名数据
		/// </summary>
		/// <returns>The message.</returns>
		/// <param name="model">Model.</param>
		public string SrcMsg(klt_gateway_notify model) {
			string json = model.ToJson();
			var collection = json.ToObject<Dictionary<string, object>>();
			collection.Remove("signMsg");
			string result = string.Empty;
			foreach (var kv in collection) {
				if (kv.Value != null && !string.IsNullOrEmpty(kv.Value.ToString()))
					result += "&" + kv.Key + "=" + kv.Value;
			}
			return result.TrimStart('&');
		}
		/// <summary>
		/// 验证sign.
		/// </summary>
		/// <returns><c>true</c>, if sign was verifyed, <c>false</c> otherwise.</returns>
		/// <param name="srcMsg">Source message.</param>
		/// <param name="signMsg">Sign message.</param>
		/// <param name="certPath">Cert path.</param>
		/// <param name="isAbsolatePath">If set to <c>true</c> is absolate path.</param>
		public bool VerifySign(String srcMsg, String signMsg, String certPath, Boolean isAbsolatePath)
		{
			//base64解码签名串
			Byte[] signMsgBytes = decode(signMsg);

			RSACryptoServiceProvider rsa = new RSACryptoServiceProvider();
			//读取x509证书
			X509Certificate2 x509 = new X509Certificate2();
			if (isAbsolatePath)
			{
				//设置证书的绝对路径
				//x509.Import(@"c:\Projects\MyWebSite\cert\TLCert.cer");
				x509.Import(certPath);
			}
			else
			{
				//或者设置证书的相对路径
				//x509.Import(HttpContext.Current.Server.MapPath("../cert/TLCert.cer"));
				x509.Import(HttpContext.Current.Server.MapPath(certPath));
			}

			//x509.PublicKey.Key.ToXmlString();
			//灌注到rsa
			rsa.FromXmlString(x509.PublicKey.Key.ToXmlString(false));
			bool verifyResult = rsa.VerifyData(System.Text.Encoding.UTF8.GetBytes(srcMsg), "SHA1", signMsgBytes);

			return verifyResult;
		}

		//---------------------------------------BASE64------------------------------------------------------------

		/// <summary> Traverse the String until hitting the next Base64 character.
		/// Assumes that there is still another valid Base64 character
		/// left in the String.
		/// </summary>
		private char NextUsefulChar
		{
			get
			{
				char result = '_'; // Start with a non-Base64 character
				while (!isUsefulChar(result))
				{
					result = mString[mIndex++];
				}

				return result;
			}
		}

		/// <summary> Byte value that maps to 'a' in Base64 encoding
		/// </summary>
		private const int LOWER_CASE_A_VALUE = 26;

		/// <summary> Byte value that maps to '0' in Base64 encoding
		/// </summary>
		private const int ZERO_VALUE = 52;

		/// <summary> Byte value that maps to '+' in Base64 encoding
		/// </summary>
		private const int PLUS_VALUE = 62;

		/// <summary> Byte value that maps to '/' in Base64 encoding
		/// </summary>
		private const int SLASH_VALUE = 63;

		/// <summary> Bit mask for one character worth of bits in Base64 encoding.
		/// Equivalent to binary value 111111b.
		/// </summary>
		private const int SIX_BIT_MASK = 63;

		/// <summary> Bit mask for one byte worth of bits in Base64 encoding.
		/// Equivalent to binary value 11111111b.
		/// </summary>
		private const int EIGHT_BIT_MASK = 0xFF;

		/// <summary> The input String to be decoded
		/// </summary>
		private System.String mString;

		/// <summary> Current position in the String(to be decoded)
		/// </summary>
		private int mIndex = 0;

		/// <summary> Decode an input String using Base64
		/// </summary>
		/// <param name="data">The String to be decoded
		/// </param>
		/// <returns> The appropriate byte array
		/// 
		/// </returns>
		public byte[] decode(System.String data)
		{
			mString = data;
			mIndex = 0;

			/// <summary> Total number of Base64 characters in the input
			/// </summary>
			int mUsefulLength = 0;
			int length = mString.Length;
			for (int i = 0; i < length; i++)
			{
				if (isUsefulChar(mString[i]))
				{
					mUsefulLength++;
				}
			}

			//mString = data;


			// A Base64 byte array is 75% the size of its String representation
			int byteArrayLength = mUsefulLength * 3 / 4;

			byte[] result = new byte[byteArrayLength];

			int byteTriplet = 0;
			int byteIndex = 0;

			// Continue until we have less than 4 full characters left to
			// decode in the input.
			while (byteIndex + 2 < byteArrayLength)
			{

				// Package a set of four characters into a byte triplet
				// Each character contributes 6 bits of useful information
				byteTriplet = mapCharToInt(NextUsefulChar);
				byteTriplet <<= 6;
				byteTriplet |= mapCharToInt(NextUsefulChar);
				byteTriplet <<= 6;
				byteTriplet |= mapCharToInt(NextUsefulChar);
				byteTriplet <<= 6;
				byteTriplet |= mapCharToInt(NextUsefulChar);

				// Grab a normal byte (eight bits) out of the byte triplet
				// and put it in the byte array
				result[byteIndex + 2] = (byte)(byteTriplet & EIGHT_BIT_MASK);
				byteTriplet >>= 8;
				result[byteIndex + 1] = (byte)(byteTriplet & EIGHT_BIT_MASK);
				byteTriplet >>= 8;
				result[byteIndex] = (byte)(byteTriplet & EIGHT_BIT_MASK);
				byteIndex += 3;
			}

			// Check if we have one byte left to decode
			if (byteIndex == byteArrayLength - 1)
			{
				// Take out the last two characters from the String
				byteTriplet = mapCharToInt(NextUsefulChar);
				byteTriplet <<= 6;
				byteTriplet |= mapCharToInt(NextUsefulChar);

				// Remove the padded zeros
				byteTriplet >>= 4;
				result[byteIndex] = (byte)(byteTriplet & EIGHT_BIT_MASK);
			}

			// Check if we have two bytes left to decode
			if (byteIndex == byteArrayLength - 2)
			{
				// Take out the last three characters from the String
				byteTriplet = mapCharToInt(NextUsefulChar);
				byteTriplet <<= 6;
				byteTriplet |= mapCharToInt(NextUsefulChar);
				byteTriplet <<= 6;
				byteTriplet |= mapCharToInt(NextUsefulChar);

				// Remove the padded zeros
				byteTriplet >>= 2;
				result[byteIndex + 1] = (byte)(byteTriplet & EIGHT_BIT_MASK);
				byteTriplet >>= 8;
				result[byteIndex] = (byte)(byteTriplet & EIGHT_BIT_MASK);
			}

			return result;
		}

		/// <summary> Convert a Base64 character to its 6 bit value as defined by the mapping.
		/// </summary>
		/// <param name="c">Base64 character to decode
		/// </param>
		/// <returns> int representation of 6 bit value
		/// 
		/// </returns>
		private int mapCharToInt(char c)
		{
			if (c >= 'A' && c <= 'Z')
			{
				return c - 'A';
			}

			if (c >= 'a' && c <= 'z')
			{
				return (c - 'a') + LOWER_CASE_A_VALUE;
			}

			if (c >= '0' && c <= '9')
			{
				return (c - '0') + ZERO_VALUE;
			}

			if (c == '+')
			{
				return PLUS_VALUE;
			}

			if (c == '/')
			{
				return SLASH_VALUE;
			}

			throw new System.ArgumentException(c + " is not a valid Base64 character.");
		}

		/// <summary> Convert a Base64 character to its 6 bit value as defined by the mapping.
		/// </summary>
		/// <param name="b">Base64 character to decode
		/// </param>
		/// <returns> int representation of 6 bit value
		/// 
		/// </returns>
		private char mapByteToChar(byte b)
		{
			if (b < LOWER_CASE_A_VALUE)
			{
				return (char)('A' + b);
			}

			if (b < ZERO_VALUE)
			{
				return (char)('a' + (b - LOWER_CASE_A_VALUE));
			}

			if (b < PLUS_VALUE)
			{
				return (char)('0' + (b - ZERO_VALUE));
			}

			if (b == PLUS_VALUE)
			{
				return '+';
			}

			if (b == SLASH_VALUE)
			{
				return '/';
			}

			throw new System.ArgumentException("Byte " + b + " is not a valid Base64 value");
		}

		/// <param name="c">Character to be examined
		/// </param>
		/// <returns> Whether or not the character is a Base64 character
		/// 
		/// </returns>
		private bool isUsefulChar(char c)
		{
			return (c >= 'A' && c <= 'Z') || (c >= 'a' && c <= 'z') || (c >= '0' && c <= '9') || (c == '+') || (c == '/');
		}

		/// <summary> Convert a byte to an integer.  Needed because in Java bytes
		/// are signed, and for Base64 purposes they are not.  If not done
		/// this way, when converted to an int, 0xFF will become -127
		/// </summary>
		/// <param name="b">Byte value to be converted
		/// </param>
		/// <returns> Value as an integer, as if byte was unsigned
		/// 
		/// </returns>
		private int convertUnsignedByteToInt(byte b)
		{
			if (b >= 0)
			{
				return (int)b;
			}

			return 256 + b;
		}
	}
}
